home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 13316 / 13316.xpi / content / treeViewDrawing.js < prev   
Encoding:
JavaScript  |  2009-07-25  |  30.5 KB  |  937 lines

  1.  
  2. /* Copyright (C) 2009 Norman Solomon
  3.  * e-mail: historytree.addon@yahoo.com
  4.  * 
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the License.
  12.  */
  13.  
  14. // **************************************************************************
  15. // *****                                                                *****
  16. // *****      TREE-VIEW DRAWING FUNCTIONS - DRAWING ON THE gCanvas      *****
  17. // *****    --------------------------------------------------------    *****
  18. // *****    1) Calls "treeArranger.js" methods which layout the tree    *****
  19. // *****    2) Draws TreeNode's on gCtx and expands ".jpeg" images      *****
  20. // *****                                                                *****
  21. // **************************************************************************
  22.  
  23. // =============================================================
  24. // Lays out all TreeNode.(x,y) then "plants" and draws the tree
  25. // -------------------------------------------------------------
  26. // *** HOWEVER - Scrolling that depends on TreeNode.(x,y)
  27. // values CANNOT be done until after the tree has been laid out
  28. // =============================================================
  29. function drawTree(scrollToCorner, scrollTabID, scrollNode)
  30. {
  31.     // Init vars used in this function
  32.     var treeRoot = gTreeNodes[0];    // Assumed
  33.     var leftBorder;                    // Integer
  34.     var dimen;                        // Point()
  35.     var tNode;                        // TreeNode
  36.     var canvasHgt;                    // Integer
  37.     
  38.     // ------------------------------------------------
  39.     // Stop GV display setInterval() if its running
  40.     if (gGVdispInterval !== null)
  41.     {
  42.         clearInterval(gGVdispInterval);
  43.         gGVdispInterval = null;
  44.     }
  45.  
  46.     // ---------------------------------------------------------
  47.     // Layout tree out horizontally then changes it to vertical
  48.     // (Sven Moen's algorithm ALWAYS lays out tree horizontally)
  49.     tArrLayout(treeRoot);
  50.     changeTreeToVerticalLayout(treeRoot);
  51.     
  52.     // Get the tree's dimensions as a Point(width, height)
  53.     dimen = treeDimensions();
  54.  
  55.     // Set <canvas...> width to enable full tree viewing
  56.     // Also making sure that gCanvas fills the screen width
  57.     if (dimen.x + 40 < CANVAS_MIN_WID)
  58.         gCanvas.width = CANVAS_MIN_WID;
  59.     else
  60.         gCanvas.width = dimen.x + 40;
  61.     
  62.     // Set <canvas...> height, where the Y add on allows the 
  63.     // user to view the lowest expanded hNode ".jpeg" image
  64.     canvasHgt = Math.round(dimen.y + (MAX_IMG_WID * 0.65) + 70);
  65.     if (canvasHgt < CANVAS_MIN_HGT)
  66.         gCanvas.height = CANVAS_MIN_HGT;
  67.     else
  68.         gCanvas.height = canvasHgt;
  69.     
  70.     // Centralize the tree horizontally on the <canvas...>
  71.     leftBorder = Math.round((gCanvas.width - dimen.x) / 2);
  72.     setLeftAndTopBorderForWholeTree(leftBorder, 28);
  73.  
  74.     // ----------------------------------------------------
  75.     // Clear the gCanvas - i.e. the only window <canvas...>
  76.     gCtx.fillStyle = CANVAS_BAK_COL;  // Dark grey
  77.     gCtx.fillRect(0, 0, gCanvas.width, gCanvas.height);
  78.  
  79.     // Scroll using passed params and/or TreeNode.(x,y) values
  80.     if (scrollTabID !== null)
  81.     {
  82.         // Scroll passed scrollTabID into view
  83.         scrollTabIntoView(scrollTabID);
  84.     }    
  85.     else if (scrollNode !== null)
  86.     {
  87.         // Scroll passed scrollNode into view 
  88.         scrollNodeIntoView(scrollNode);
  89.     }
  90.     else if (scrollToCorner)
  91.     {
  92.         // Scroll to top-left corner 
  93.         gCanvasScroller.scrollTo(0, 0);
  94.     }
  95.  
  96.     // ---------------------------------------------------
  97.     // Draw all TreeNode's in gTreeNodes[] on the gCanvas
  98.     for (var i = 0; i < gTreeNodes.length; i++)
  99.     {
  100.         // Get the TreeNode
  101.         tNode = gTreeNodes[i];
  102.  
  103.         // Don't draw TreeNode if its marked as hidden
  104.         if (!tNode.hidden)
  105.         {
  106.             // Draw child connection lines for parent nodes
  107.             // (best done first to avoid node box overlaps)
  108.             if (tNode.child !== null)
  109.                 drawChildConnectionLinesFor(tNode);
  110.             
  111.             // Draw node box and the text inside it
  112.             drawNodeBoxWithTextInside(tNode, true);
  113.         }
  114.     }
  115.  
  116.     // Set global flag to show that expanded image is cleared
  117.     gExpandedNode = null;
  118. }
  119.  
  120. // ===================================================
  121. // Scrolls Tab with passed tabID into view on screen
  122. // ===================================================
  123. function scrollTabIntoView(tabID)
  124. {
  125.     // TreeNode and integers
  126.     var tNode;
  127.     var minX = 0;
  128.     var maxX = 0;
  129.     var tabScrollX;
  130.     
  131.     // --------------------------------------------------
  132.     // Get (minX, maxX) for Tab sub-tree drawn on gCanvas
  133.     for (var i = 1; i < gTreeNodes.length; i++)
  134.     {
  135.         // Only check visible TreeNodes with passed tabID
  136.         tNode = gTreeNodes[i];
  137.         if (!tNode.hidden && tNode.histNode.tab === tabID)
  138.         {
  139.             if (tNode.pos.x + tNode.width > maxX)
  140.                 maxX = tNode.pos.x + tNode.width;
  141.  
  142.             if (minX === 0 || tNode.pos.x < minX)
  143.                 minX = tNode.pos.x;
  144.         }
  145.     }
  146.     
  147.     // Scroll the Tab into view
  148.     if (maxX <= CANVAS_MIN_WID)
  149.     {
  150.         // Tab fits on screen when scrolled to (0,0)
  151.         tabScrollX = 0;
  152.     }
  153.     else
  154.     {
  155.         // Tab is off screen to the right
  156.         if (maxX - minX > CANVAS_MIN_WID)
  157.             tabScrollX = minX - 20;
  158.         else
  159.             tabScrollX = maxX - CANVAS_MIN_WID + 20;
  160.     }
  161.     
  162.     // Scroll Tab into view. The setTimeout() is needed 
  163.     // because the scrollbar can take while to appear?!
  164.     setTimeout(function() 
  165.     {
  166.         clearTimeout();    // Essential
  167.         gCanvasScroller.scrollTo(tabScrollX, 0);
  168.     }, 100);
  169. }
  170.  
  171. // ==============================================================
  172. // Draws all connection lines between a parent and her children 
  173. // ==============================================================
  174. function drawChildConnectionLinesFor(node)
  175. {
  176.     // Set line color and thickness for all connection lines
  177.     gCtx.strokeStyle = "rgb(206,255,153)";    // Light green
  178.     gCtx.lineWidth = 2;                        // Looks best
  179.  
  180.     // If only one child draw conn to parent, then exit
  181.     var child = node.child;
  182.     var centerX, vLineX;
  183.     
  184.     if (child.sibling === null)
  185.     {
  186.         centerX = Math.round(child.pos.x + BOX_WIDTH / 2);
  187.         drawLine(centerX, child.pos.y, centerX, node.pos.y + node.height + 7);
  188.         return;
  189.     }
  190.  
  191.     // --------------------------------------------------------------
  192.     // Initialize vars for multiple children connection line drawing
  193.     var firstChild = node.child;
  194.     var firstX = Math.round(child.pos.x + BOX_WIDTH / 2);
  195.     var levelGap = Math.round((child.pos.y - (node.pos.y + node.height)) / 2);
  196.     var vLineYend = child.pos.y - levelGap + 3;  // Adjusts horiz bar Y pos
  197.     var radius = 8; // Horizontal line corner curve size
  198.     
  199.     // Draw vertical conn lines for all siblings - except the last one
  200.     while (child.sibling != null)
  201.     {
  202.         // Draw vertical conn line to center of level gap for this child
  203.         centerX = Math.round(child.pos.x + BOX_WIDTH / 2);
  204.         if (child === firstChild)
  205.             drawLine(centerX, child.pos.y, centerX, vLineYend + radius - 1);
  206.         else
  207.             drawLine(centerX, child.pos.y, centerX, vLineYend);
  208.  
  209.         // Get next sibling
  210.         child = child.sibling;
  211.     }
  212.  
  213.     // Draw vertical conn line to center of level gap for last child
  214.     centerX = Math.round(child.pos.x + BOX_WIDTH / 2);
  215.     drawLine(centerX, child.pos.y, centerX, vLineYend + radius - 1);    
  216.  
  217.     // ------------------------------------------------------------------
  218.     // Draw horiz line above all children just below center of level gap
  219.     drawLine(firstX + radius, vLineYend, centerX - radius, vLineYend);
  220.     
  221.     // Draw horiz line corner curves - first right, then left
  222.     gCtx.beginPath();
  223.     gCtx.moveTo(centerX - radius, vLineYend);
  224.     gCtx.quadraticCurveTo(centerX, vLineYend, centerX, vLineYend + radius);
  225.     gCtx.moveTo(firstX + radius, vLineYend);
  226.     gCtx.quadraticCurveTo(firstX, vLineYend, firstX, vLineYend + radius);
  227.     gCtx.stroke();
  228.  
  229.     // Draw vertical conn from center child to parent (middle of horiz line)
  230.     vLineX = Math.round(firstX + (centerX - firstX) / 2);
  231.     drawLine(vLineX, vLineYend, vLineX, node.pos.y + node.height + 7);
  232. }
  233.  
  234. // ======================================================
  235. // Draws a TreeView node box and the text inside it
  236. // ======================================================
  237. function drawNodeBoxWithTextInside(tNode, showHighLight)
  238. {
  239.     // Init vars
  240.     var boxFill, bordCol;
  241.     
  242.     // ----------------------------------------------
  243.     // Don't draw TreeNode if its marked as hidden
  244.     if (!tNode.hidden)
  245.     {
  246.         // Set colours to use when drawing tree box
  247.         if (showHighLight && isHighlightedNode(tNode))
  248.         {
  249.             // Highlight node found when user clicked Find Next/Prev
  250.             boxFill = "rgb(184,225,174)";    // Light Green
  251.             bordCol = "rgb(230,0,46)";        // Bright Red
  252.         }
  253.         else if (tNode.parent === null || !tNode.isOpenPage)
  254.         {
  255.             // "Dummy" root or node representing closed FF page
  256.             boxFill = "rgb(200,200,200)";    // Light grey
  257.             bordCol = "rgb(0,0,128)";        // Dark blue
  258.         }
  259.         else if (tNode.histNode === gParamArray[1])
  260.         {
  261.             // Page currently visible in current FF Tab
  262.             boxFill = CURR_TAB_COL;            // Orange/Red
  263.             bordCol = "rgb(230,0,46)";        // Bright Red
  264.         }
  265.         else
  266.         {
  267.             // Open FF page (not root and not current FF page)
  268.             boxFill = "rgb(250,250,250)";    // Off white
  269.             bordCol = "rgb(2,102,0)";        // Dark green
  270.         }
  271.  
  272.         // Draw node box using set colours
  273.         drawNodeBox(tNode, boxFill, bordCol);
  274.  
  275.         // Draw text inside node
  276.         drawDescriptionInNodeBox(tNode, "rgb(160,0,0)");
  277.     }
  278. }
  279.  
  280. // ==========================================================
  281. // Draws a TreeNode on the gCanvas - Representing a FF page
  282. // ==========================================================
  283. function drawNodeBox(node, boxFill, bordCol)
  284. {
  285.     // Initialize box-button and box-panel drawing vars
  286.     var panFill;                        // Top-panel colour
  287.     var btnY, btnX, leftBtnWid;            // Button drawing integers
  288.     var btnFill = "rgb(200,200,200)";    // Button fill colour (grey)
  289.     var textCol = "rgb(0,0,128)";        // Button text colour (blue)
  290.     var txtNum;                            // Button text
  291.  
  292.     // -------------------------------------------------------
  293.     // Draw node box as rounded-corner rect with inner border
  294.     gCtx.lineWidth = 2;
  295.     if (node.parent === null)
  296.     {
  297.         // "Dummy" root - Taller with light grey fill
  298.         drawRoundedRectWithBorder
  299.             (node.pos.x, node.pos.y - 8, 
  300.              node.width, node.height + 8, 
  301.              9, "rgb(225,225,225)", bordCol);
  302.     }
  303.     else
  304.     {
  305.         // Not "Dummy" root - Shorter with passed boxFill
  306.         drawRoundedRectWithBorder
  307.             (node.pos.x, node.pos.y, 
  308.              node.width, node.height, 
  309.              9, boxFill, bordCol);
  310.     }
  311.  
  312.     // ---------------------------------------------------------
  313.     // Draw top panel area if passed node is a Tab sub-tree root
  314.     if (node.height > BOX_HEIGHT)
  315.     {
  316.         // Set header panel fill color depending on Tab root type
  317.         if (node.histNode.tab === gCurrentTabID)
  318.             panFill = CURR_TAB_COL;        // Orange
  319.         else if (node.inOpenTab)
  320.             panFill = OPEN_TAB_COL;        // Yellow
  321.         else
  322.             panFill = CLOSED_TAB_COL;    // Blue
  323.         
  324.         // Draw Tab root node header panel
  325.         drawTabHeader
  326.             (node.pos.x + 1, node.pos.y + 1, 
  327.              node.width - 2, node.height - BOX_HEIGHT, 
  328.              9, panFill, bordCol);
  329.     }
  330.  
  331.     // ----------------------------------------------------------
  332.     // Draw box-buttons at bottom of node box - with "up-arrow"
  333.     // shape or text inside. First, set y for drawing all buttons
  334.     btnY = Math.round(node.pos.y + node.height - (BOX_BTN_HGT / 2));
  335.     
  336.     // Draw one or two box-buttons
  337.     gCtx.lineWidth = 2; 
  338.     if (node.child === null && node.hiddenTreeSize === 0)
  339.     {
  340.         // Leaf node - So only draw view web-page "?" button 
  341.         btnX = Math.round(node.pos.x + (node.width - BOX_BTN_WID) / 2);
  342.         drawRoundedRectWithBorder
  343.             (btnX, btnY, BOX_BTN_WID, BOX_BTN_HGT, 6, btnFill, bordCol);
  344.         
  345.         // Draw "?" inside view web-page button
  346.         drawMessageOnCanvasAt
  347.             (btnX + 7, btnY + 12, "?", "bold 9pt arial", textCol);
  348.  
  349.         // Set TreeNode props to enable fast detection of mouse clicks
  350.         node.btnPanelX = btnX;
  351.         node.hidBtnWid = 0;
  352.         node.btnPanelWid = BOX_BTN_WID;
  353.     }
  354.     else
  355.     {
  356.         // Node with children - which can visible or hidden
  357.         // Draw hidden sub-tree size or "up-arrow" inside button
  358.         if (node.hiddenTreeSize > 1) // Hidden children
  359.         {
  360.             // Adjust left box width to allow printing of sub-tree size
  361.             txtNum = (node.hiddenTreeSize - 1).toString();
  362.             if ((node.hiddenTreeSize - 1) > 9)
  363.                 leftBtnWid = gCtx.mozMeasureText(txtNum) + 15;    // Wider for > 9
  364.             else
  365.                 leftBtnWid = BOX_BTN_WID;  // Normal width for < 10
  366.  
  367.             // ****  COMMENT THESE LINES IN TO TEST LARGE SUB-TREE SIZES  ****
  368.             //txtNum = (12345).toString();
  369.             //leftBtnWid = gCtx.mozMeasureText(txtNum) + 15;
  370.  
  371.             // Draw button that shows hidden sub-tree size
  372.             btnX = Math.round(node.pos.x + (node.width - leftBtnWid - BOX_BTN_WID) / 2);
  373.             drawRoundedRectWithBorder
  374.                 (btnX, btnY, leftBtnWid, BOX_BTN_HGT, 6, btnFill, bordCol);
  375.             
  376.             // Draw hidden sub-tree size inside button
  377.             drawMessageOnCanvasAt
  378.                 (btnX + 7, btnY + 11, txtNum, "7pt verdana", "rgb(230,0,0)");
  379.         }
  380.         else
  381.         {
  382.             // Node has no hidden children - Draw button with "up-arrow"
  383.             leftBtnWid = BOX_BTN_WID;
  384.             btnX = Math.round(node.pos.x + (node.width - BOX_BTN_WID * 2) / 2);
  385.             drawRoundedRectWithBorder
  386.                 (btnX, btnY, BOX_BTN_WID, BOX_BTN_HGT, 6, btnFill, bordCol);
  387.  
  388.             // Draw blue filled "up arrow" shape
  389.             gCtx.beginPath();
  390.             gCtx.moveTo(btnX + 5, btnY + 11);
  391.             gCtx.lineTo(btnX + 16, btnY + 11);
  392.             gCtx.lineTo(btnX + 10, btnY + 4);
  393.             gCtx.fillStyle = textCol;
  394.             gCtx.fill();
  395.         }
  396.  
  397.         // Set TreeNode props to enable fast detection of user mouse clicks
  398.         node.btnPanelX = btnX;
  399.         node.hidBtnWid = leftBtnWid;
  400.         node.btnPanelWid = leftBtnWid + BOX_BTN_WID;
  401.  
  402.         // Draw button used to show web-page image
  403.         btnX += leftBtnWid;
  404.         drawRoundedRectWithBorder
  405.             (btnX, btnY, BOX_BTN_WID, BOX_BTN_HGT, 6, btnFill, bordCol);
  406.         
  407.         // Draw "?" inside button
  408.         drawMessageOnCanvasAt
  409.             (btnX + 7, btnY + 12, "?", "bold 9pt arial", textCol);
  410.     }
  411. }
  412.  
  413. // =======================================================
  414. // Draw TreeNode description lines inside TreeNode box
  415. // =======================================================
  416. function drawDescriptionInNodeBox(node, txtCol)
  417. {
  418.     // Text and time info appearance
  419.     var timeCol = "rgb(0,0,110)";        // Dark blue
  420.     var txtFont = "bold 7pt verdana";    // Bold
  421.     var timeFont = "7pt verdana";        // Not bold
  422.     var timeInfo = "";
  423.     var tabHdrMsg = "";
  424.     var yAddOn = 0;
  425.  
  426.     // -------------------------------------------------
  427.     // Draw session info, then exit if node is tree root
  428.     if (node.histNode === null)
  429.     {
  430.         drawSessionInfoInTreeRootBox(node);
  431.         return;
  432.     }
  433.     
  434.     // Rebuild TreeNode.descLine's if not set, or need updating
  435.     if (node.histNodeDesc !== node.histNode.desc
  436.      || node.histNode.desc === "")
  437.     {
  438.         setDescriptionLinesFor(node, txtFont);
  439.         node.histNodeDesc = node.histNode.desc;
  440.     }
  441.  
  442.     // -------------------------------------------------
  443.     // Draw text in Tab root node box header panel
  444.     if (node.height > BOX_HEIGHT)
  445.     {
  446.         // Set header panel text depending on Tab root type
  447.         if (node.histNode.tab === gCurrentTabID)
  448.             tabHdrMsg = "Current";
  449.         else if (node.inOpenTab)
  450.             tabHdrMsg = "Open";
  451.         else
  452.             tabHdrMsg = "Closed";
  453.  
  454.         // Add number of TreeNode's in Tab sub-tree to header text
  455.         tabHdrMsg += " - " + (node.tabTreeSize).toString();
  456.         if (node.tabTreeSize > 1)
  457.             tabHdrMsg += " pages";
  458.         else
  459.             tabHdrMsg += " page";            
  460.         
  461.         // Draw text in panel and set yAddOn for text below
  462.         drawNodeText(node, 14, tabHdrMsg, txtFont, "black");
  463.         yAddOn = node.height - BOX_HEIGHT;
  464.     }
  465.  
  466.     // ---------------------------------------------
  467.     // Get time web-page was first loaded into FF
  468.     timeInfo = node.histNode.timeInfo;
  469.     
  470.     // Make txtCol red darker for current FF page
  471.     if (node.histNode === gParamArray[1])
  472.         txtCol = "rgb(120,0,0)";
  473.  
  474.     // Draw one or two lines of text at hard coded Y
  475.     if (node.descLine2 === "")
  476.     {
  477.         // Draw line1 only
  478.         drawNodeText(node, 25 + yAddOn, node.descLine1, txtFont, txtCol);
  479.  
  480.         // Draw time info
  481.         drawNodeText(node, 40 + yAddOn, timeInfo, timeFont, timeCol);
  482.     }
  483.     else
  484.     {
  485.         // Draw both lines
  486.         drawNodeText(node, 19 + yAddOn, node.descLine1, txtFont, txtCol);
  487.         drawNodeText(node, 32 + yAddOn, node.descLine2, txtFont, txtCol);
  488.  
  489.         // Draw time info
  490.         drawNodeText(node, 47 + yAddOn, timeInfo, timeFont, timeCol);
  491.     }
  492. }
  493.  
  494. // =============================================================
  495. // Draws header panel for all Tab sub-tree root TreeNode's
  496. // =============================================================
  497. function drawTabHeader(x, y, w, h, radius, fillCol, bordCol)
  498. {
  499.     // Draw filled rectangle with rounded top corners
  500.     gCtx.beginPath();
  501.     gCtx.moveTo(x, y + radius);
  502.     gCtx.lineTo(x, y + h);    
  503.     gCtx.lineTo(x + w, y + h);
  504.     gCtx.lineTo(x + w, y + radius);
  505.     gCtx.quadraticCurveTo(x + w, y, x + w - radius, y);
  506.     gCtx.lineTo(x + radius, y);
  507.     gCtx.quadraticCurveTo(x, y, x, y + radius);    
  508.     gCtx.fillStyle = fillCol;
  509.     gCtx.fill();
  510.  
  511.     // Draw a line below the rectangle
  512.     gCtx.lineWidth = 1;
  513.     gCtx.beginPath();
  514.     gCtx.moveTo(x, y + h);
  515.     gCtx.lineTo(x + w, y + h);
  516.     gCtx.strokeStyle = bordCol;
  517.     gCtx.stroke();
  518. }
  519.  
  520. // ===========================================================
  521. // Draw page-visited and Tab-count stats in "dummy" tree root
  522. // ===========================================================
  523. function drawSessionInfoInTreeRootBox(node)
  524. {
  525.     // Text and time info appearance
  526.     var infoLen, numOpen, printX;        // Integers
  527.     var txtCol = "rgb(160,0,0)";        // Dark red
  528.     var txtFont = "bold 7pt verdana";    // Bold
  529.     var infoFont = "7pt verdana";        // Not bold
  530.     var infoStat = "";                    // See below
  531.  
  532.     // X to print at depends on variable width of num pages
  533.     gCtx.mozTextStyle = infoFont;
  534.     infoStat = (gTreeNodes.length - 1).toString();
  535.     infoLen = gCtx.mozMeasureText(infoStat);
  536.     printX = Math.round(node.pos.x + (BOX_WIDTH - infoLen) / 2);
  537.  
  538.     // Draw info labels in info box
  539.     gCtx.mozTextStyle = txtFont;
  540.     gCtx.fillStyle = txtCol;
  541.     fillText("Pages visited:", printX - 41, node.pos.y + 15);
  542.     fillText("Tabs now open:", printX - 42, node.pos.y + 29);
  543.     fillText("Tabs closed:", printX - 42, node.pos.y + 43);
  544.  
  545.     // Draw info counts in info box
  546.     gCtx.mozTextStyle = infoFont;
  547.     gCtx.fillStyle = "rgb(0,0,110)";
  548.     
  549.     // Num pages visited is size of gTreeNodes[] - 1
  550.     infoStat = (gTreeNodes.length - 1).toString();
  551.     fillText(infoStat, printX + 42, node.pos.y + 15);
  552.     
  553.     // Show count of number of open Tabs in gTabRoots[]
  554.     numOpen = numExtWinOpenTabs();
  555.     infoStat = numOpen.toString();
  556.     fillText(infoStat, printX + 42, node.pos.y + 29);
  557.     
  558.     // Show number of closed Tabs in gTabRoots[]
  559.     infoStat = (gTabRoots.length - numOpen).toString();
  560.     fillText(infoStat, printX + 42, node.pos.y + 43);
  561. }
  562.  
  563. // ===========================================================
  564. // Draws FF session info box below tree root TreeNode box
  565. // Drawn instead of expanded image, for tree root node only
  566. // ===========================================================
  567. function drawSessInfoBox(node)
  568. {
  569.     // Calculate (x,y) position to show info box at
  570.     var infoBoxHgt = 85;
  571.     var infoBoxWid = 192;        
  572.     var x = Math.round(node.pos.x - (infoBoxWid - node.width) / 2);
  573.     var y = Math.round(node.pos.y + node.height + BOX_BTN_HGT / 2 + 2);
  574.  
  575.     // Save global vars used to detect mouse-clicks on info box
  576.     gImgXY = new Point(x, y);
  577.     gInfoBoxWH = new Point(infoBoxWid, infoBoxHgt);
  578.  
  579.     // -------------------------------------------------
  580.     // Drawing colours and fonts
  581.     var panFill = "rgb(180,190,220)";    // Blue
  582.     var boxFill = "rgb(225,225,225)";    // Light Grey
  583.     var bordCol = "rgb(0,0,128)";        // Dark blue
  584.     var txtCol = "rgb(160,0,0)";        // Dark red
  585.     var txtFont = "bold 7pt verdana";    // Bold
  586.     var infoFont = "7pt verdana";        // Not bold
  587.     
  588.     // Session timing information vars
  589.     var sessStartDate = Application.storage.get
  590.         ("FFstartDate", new Date());    // Set when FF starts
  591.     var sessTimeInfo;                    // Drawn in info box
  592.  
  593.     // -------------------------------------------------
  594.     // Draw info box
  595.     gCtx.lineWidth = 2;
  596.     drawRoundedRectWithBorder
  597.         (x, y, infoBoxWid, infoBoxHgt, 9, boxFill, bordCol);
  598.  
  599.     // Draw top panel area
  600.     drawTabHeader
  601.         (x + 1, y + 1, infoBoxWid - 2, 20, 9, panFill, bordCol);
  602.  
  603.     // Draw label in top panel area
  604.     gCtx.mozTextStyle = txtFont;
  605.     gCtx.fillStyle = txtCol;
  606.     fillText("Browsing Session Times", x + 38, y + 15);
  607.  
  608.     // Draw info labels in box
  609.     fillText("Start date:", x + 20, y + 41);
  610.     fillText("Start time:", x + 20, y + 56);
  611.     fillText("Duration:", x + 20, y + 71);
  612.     
  613.     // Draw start date - e.g. "Fri Apr 24 2009" etc.
  614.     gCtx.mozTextStyle = infoFont;
  615.     gCtx.fillStyle = "rgb(0,0,110)";
  616.     sessTimeInfo = sessStartDate.toDateString();
  617.     fillText(sessTimeInfo, x + 84, y + 41);
  618.     
  619.     // Draw start time - e.g. "16:45" etc.
  620.     sessTimeInfo = sessStartDate.toTimeString();
  621.     fillText(sessTimeInfo.substr(0,5), x + 84, y + 56);
  622.     
  623.     // Draw session duration - e.g. "1 hour 2 min" etc.
  624.     sessTimeInfo = browsingSessionDuration(sessStartDate);
  625.     fillText(sessTimeInfo, x + 84, y + 71);
  626. }
  627.  
  628. // =========================================================
  629. // Constructs and returns Firefox browsing session duration 
  630. // =========================================================
  631. function browsingSessionDuration(sessStartDate)
  632. {
  633.     // Init vars
  634.     var timeNow = new Date();
  635.     var secsNow, startedSecs;
  636.     var minSpent, hourSpent, minOver;
  637.  
  638.     // ---------------------------------------------
  639.     // Get time to nearest second from both dates
  640.     secsNow = Math.round(timeNow.getTime() / 1000);
  641.     startedSecs = Math.round(sessStartDate.getTime() / 1000);
  642.  
  643.     // Subtract times to get min spent (to nearest 0.1 of minute)
  644.     minSpent = ((secsNow - startedSecs) / 60).toFixed(1);
  645.     
  646.     // Construct and return browsing session duration
  647.     if (minSpent > 60)
  648.     {
  649.         // More than 60 mins - Format is "1 hour 1 min" etc.
  650.         hourSpent = Math.floor(minSpent / 60);
  651.         minOver = Math.floor(minSpent - hourSpent * 60);
  652.         return hourSpent.toString() + " hour " 
  653.              + minOver.toString() + " min";
  654.     }
  655.     else if (minSpent === 0)
  656.     {
  657.         // Less than 6 seconds - Format is "5 seconds" etc.
  658.         return (secsNow - startedSecs).toString() + " seconds";
  659.     }
  660.     else
  661.     {
  662.         // Up to 60 mins - Format is "1.2 min" etc.
  663.         return minSpent.toString() + " min";
  664.     }
  665. }
  666.  
  667. // ============================================================
  668. // Sets node.descLine1 and node.descLine2 for passed TreeNode
  669. // ============================================================
  670. function setDescriptionLinesFor(node, font)
  671. {
  672.     // Get trimmed desc or URI from associated HistoryNode
  673.     var txt = trim(node.histNode.desc);
  674.     if (txt === "")    // Use URI instead
  675.         txt = trim(node.histNode.uri);
  676.  
  677.     // Set both node lines then exit if trimmed desc fits on one line
  678.     var maxWid = BOX_WIDTH - 26;
  679.     gCtx.mozTextStyle = font;
  680.     if (gCtx.mozMeasureText(txt) <= maxWid)
  681.     {
  682.         node.descLine1 = txt;
  683.         node.descLine2 = "";
  684.         return;
  685.     }
  686.  
  687.     // -------------------------------------------------------
  688.     // Descrip does NOT fit on one line - So create two lines
  689.     var txtLen = txt.length;
  690.     var chrNum = 0;
  691.     var lastSepNdx = 0;
  692.     var line1 = "";
  693.     var chr = "";
  694.     var tempTxt = "";
  695.  
  696.     // Keep adding chars to line1 until its width >= maxWid
  697.     while (gCtx.mozMeasureText(line1) < maxWid && chrNum < txtLen)
  698.     {
  699.         chr = txt.substr(chrNum, 1);
  700.         line1 += chr;
  701.         
  702.         if (isSeperator(chr))
  703.             lastSepNdx = chrNum;
  704.         
  705.         chrNum ++;
  706.     }
  707.     
  708.     // -----------------------------------------------------
  709.     // Set both TreeNode lines depending on result of above
  710.     if (lastSepNdx === 0)
  711.     {
  712.         // Description is just one very long word
  713.         node.descLine1 = truncatedText(txt, maxWid);
  714.         node.descLine2 = "";
  715.     }
  716.     else
  717.     {
  718.         // Remove last chr if it made line1 too wide
  719.         if (gCtx.mozMeasureText(line1) > maxWid)
  720.         {
  721.             chrNum --;
  722.             line1 = txt.substr(0, chrNum);
  723.             chr = txt.substr(chrNum - 1, 1)
  724.         }
  725.  
  726.         // Change the end part of line1 as req
  727.         tempTxt = txt.substr(chrNum, 1);
  728.         if (!isSeperator(tempTxt) && !isSeperator(chr))
  729.             // Remove partial word from end of line1
  730.             line1 = txt.substr(0, lastSepNdx + 1);
  731.         else if (isSeperator(tempTxt))
  732.             // Add seperator to end of line1 (looks best)
  733.             line1 = txt.substr(0, chrNum + 1);
  734.  
  735.         // Set line2 = truncated version of remainder of desc
  736.         node.descLine1 = trim(line1);
  737.         tempTxt = txt.substr(node.descLine1.length);
  738.         node.descLine2 = truncatedText(tempTxt, maxWid);
  739.     }
  740. }
  741.  
  742. // =======================================================
  743. // Returns true if passed chr is defined as a seperator
  744. // =======================================================
  745. function isSeperator(chr)
  746. {
  747.     // Seperators could be as below (or not? or other?)
  748.     if (chr === " " || chr === "/" || chr === "," 
  749.      || chr === ":" || chr === ";" || chr === "_")
  750.         return true;
  751.     else
  752.         return false;
  753. }
  754.  
  755. // ============================================================
  756. // Draws passed txt centrally inside box drawn for passed node
  757. // ============================================================
  758. function drawNodeText(node, yAddOn, txt, font, col)
  759. {
  760.     // Centralize txt horizontally
  761.     gCtx.mozTextStyle = font;
  762.     var txtLen = gCtx.mozMeasureText(txt);
  763.     var x = Math.round(node.pos.x + (BOX_WIDTH - txtLen) / 2);
  764.  
  765.     // Add passed increment to y
  766.     var y = node.pos.y + yAddOn;
  767.  
  768.     // Draw txt inside node box
  769.     gCtx.fillStyle = col;
  770.     fillText(txt, x, y);
  771. }
  772.  
  773. // ==================================================================================
  774. // FIRST - Called from function respondToTreeViewMouseClickOnCanvas(x, y)
  775. // ----------------------------------------------------------------------------------
  776. // THEN - Called repeatedly via setInterval() during ".jpeg" image expansion
  777. // FINALLY - Terminates itself via clearInterval() when ".jpeg" width >= MAX_IMG_WID
  778. // ==================================================================================
  779. function drawExpandingNodeImageOnCanvas(node)
  780. {
  781.     // Exit if passed TreeNode is "dummy" tree root 
  782.     if (node.histNode === null)
  783.         return;
  784.  
  785.     // ------------------------------------------------------
  786.     // Constant used to increase image size *** NOTE Some
  787.     // PC's have slow graphics - So use 10 steps of 75 to 750
  788.     const ADD_IMG_WID = 75;
  789.  
  790.     // Init vars
  791.     var x, y;              // Integers
  792.     var maxWidX, imgHgt;  // Integers
  793.     var histNode;          // HistoryNode
  794.  
  795.     // -------------------------------------------------
  796.     // Calculate (x,y) position to show image at
  797.     x = Math.round(node.pos.x - (gNodeImageWid + ADD_IMG_WID - node.width) / 2);
  798.     y = Math.round(node.pos.y + node.height + BOX_BTN_HGT / 2 + 2);
  799.     
  800.     // Adjust drawn image x co-ordinate if it goes off-screen
  801.     // *** NOTE - This does NOT consider scrolled position
  802.     maxWidX = Math.round(node.pos.x - (MAX_IMG_WID - node.width) / 2);
  803.  
  804.     if (maxWidX + MAX_IMG_WID > gCanvas.width)
  805.         x -= Math.round((maxWidX + MAX_IMG_WID) - gCanvas.width) + 20;
  806.     else if (maxWidX <= 5)
  807.         x += Math.abs(maxWidX) + 20;
  808.  
  809.     // -------------------------------------------------------
  810.     // Draw image or blank rectangle if animation not complete
  811.     if (gNodeImageWid < MAX_IMG_WID)
  812.     {
  813.         // Increase image size (produces animation effect)
  814.         gNodeImageWid += ADD_IMG_WID;        
  815.         imgHgt = Math.round(gNodeImageWid * WH_RATIO);
  816.     
  817.         // Draw histNode ".jpeg" image or a white bordered rectangle
  818.         histNode = node.histNode;
  819.         if (!blankRectangleDrawn
  820.             (histNode, x, y, gNodeImageWid, imgHgt, false, "black"))
  821.         {
  822.             // Draw a black inner border to contain the image
  823.             gCtx.lineWidth = 2;
  824.             gCtx.strokeStyle = 'black';
  825.             gCtx.strokeRect(x - 1, y - 1, gNodeImageWid + 2, imgHgt + 2);
  826.  
  827.             // Draw ".jpeg" image - i.e page HAS been viewed in FF
  828.             if (!histNode.imgCreated)
  829.             {
  830.                 // Slow draw - once only
  831.                 drawAndUpdateNodeImage
  832.                     (histNode, x, y, gNodeImageWid, imgHgt);
  833.             }
  834.             else
  835.             {
  836.                 // Fast draw - all further draws
  837.                 gCtx.drawImage
  838.                     (histNode.imgToDraw, x, y, gNodeImageWid, imgHgt);
  839.             }
  840.         }
  841.  
  842.         // Save global var used to detect mouse-clicks on image
  843.         gImgXY = new Point(x, y);
  844.     }
  845.     else
  846.     {
  847.         // Stop setInterval() and set global flag
  848.         clearInterval(gImgExpandInterval);
  849.         gImgExpandInterval = null;
  850.  
  851.         // Reset global flag to show that animation has stopped
  852.         gNodeImageWid = 0;        
  853.  
  854.         // Save global var used to detect mouse-clicks on image
  855.         gInfoBoxWH = new Point(MAX_IMG_WID, MAX_IMG_WID * WH_RATIO);
  856.     }
  857. }
  858.  
  859. // ===========================================================
  860. // Draws a rounded rect with inner border (inner looks best)
  861. // ===========================================================
  862. function drawRoundedRectWithBorder
  863.     (x, y, w, h, radius, fillCol, bordCol)
  864. {
  865.     // Draw a rounded corner rectangle
  866.     drawRoundedRect(x, y, w, h, radius, fillCol, true);
  867.     
  868.     // Draw an inner border within rectangle
  869.     drawRoundedRect(x, y, w, h, radius, bordCol, false);
  870. }
  871.  
  872. // =================================================================
  873. // Draws a filled/unfilled rounded corner rect using passed params 
  874. // =================================================================
  875. function drawRoundedRect(x, y, w, h, radius, col, fill)
  876. {
  877.     // Draw a rounded corner rectangle using passed params 
  878.     gCtx.beginPath();
  879.     gCtx.moveTo(x, y + radius);
  880.     gCtx.lineTo(x, y + h - radius);
  881.     gCtx.quadraticCurveTo(x, y + h, x + radius, y + h);
  882.     gCtx.lineTo(x + w - radius, y + h);
  883.     gCtx.quadraticCurveTo(x + w, y + h, x + w, y + h - radius);
  884.     gCtx.lineTo(x + w, y + radius);
  885.     gCtx.quadraticCurveTo(x + w, y, x + w - radius, y);
  886.     gCtx.lineTo(x + radius, y);
  887.     gCtx.quadraticCurveTo(x, y, x, y + radius);
  888.     
  889.     // Fill or just outline the rectangle
  890.     if (fill)
  891.     {
  892.         gCtx.fillStyle = col;
  893.         gCtx.fill();
  894.     }
  895.     else
  896.     {
  897.         gCtx.strokeStyle = col;
  898.         gCtx.stroke();
  899.     }
  900. }
  901.  
  902. // ===========================================================
  903. // Draws a filled rect with inner border using passed params 
  904. // ===========================================================
  905. function drawBorderedRect(x, y, w, h, fillCol, bordCol, bordWid)
  906. {
  907.     // Draw filled rectangle
  908.     gCtx.fillStyle = fillCol;
  909.     gCtx.fillRect(x, y, w, h);
  910.         
  911.     // Draw rectangle border
  912.     gCtx.lineWidth = bordWid;
  913.     gCtx.strokeStyle = bordCol;
  914.     gCtx.strokeRect(x, y, w, h);
  915. }
  916.  
  917. // ====================================================
  918. // Draws a line on the gCanvas from (xf,yf) to (xt,yt)
  919. // ====================================================
  920. function drawLine(xf, yf, xt, yt)
  921. {
  922.     gCtx.beginPath();
  923.     gCtx.moveTo(xf, yf);
  924.     gCtx.lineTo(xt, yt);
  925.     gCtx.stroke();
  926. }
  927.  
  928. // ======================================================
  929. // Draws the passed msg on canvas at (x,y) in passed col
  930. // ======================================================
  931. function drawMessageOnCanvasAt(x, y, msg, font, col)
  932. {
  933.     // Show test message in passed colour at specified (x,y)
  934.     gCtx.fillStyle = col;
  935.     gCtx.mozTextStyle = font;
  936.     fillText(msg, x, y);
  937. }